home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue50 / Except / HVHookDLL.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1999-09-04  |  5.5 KB  |  154 lines

  1. unit HVHookDLL;
  2. { General DLL hooking utility
  3.  
  4.   Hook any implicitly imported DLL routine used by a loaded module.
  5.  
  6.   Written by Hallvard Vassbotn, hallvard@balder.no, September 1999
  7.  
  8.   Inspired by Matt Pietrek's article "Peering inside the PE: A Tour of the Win32
  9.   Portable Executable File Format", available at:
  10.  
  11.      http://msdn.microsoft.com/library/techart/msdn_peeringpe.htm
  12.  
  13.   Quote: "Since the import address table is in a writeable section, it's
  14.   relatively easy to intercept calls that an EXE or DLL makes to another DLL.
  15.   Simply patch the appropriate import address table entry to point at the
  16.   desired interception function. There's no need to modify any code in either
  17.   the caller or callee images. What could be easier?"
  18. }
  19. interface
  20.  
  21. function ReplaceImport(Base: Pointer; ModuleName: PChar; FromProc, ToProc: pointer): boolean;
  22. function HookImport(ModuleName, ImportName: PChar; HookProc: pointer; var DLLProc: pointer): boolean;
  23. function UnhookImport(ModuleName, ImportName: PChar; HookProc: pointer; var DLLProc: pointer): boolean;
  24.  
  25. implementation
  26.  
  27. uses
  28.   Windows,
  29.   SysUtils,
  30.   HVPEUtils;
  31.  
  32. type
  33.   PWin95CallThunk = ^TWin95CallThunk;
  34.   TWin95CallThunk = packed record
  35.     PUSH: byte;     // PUSH instruction opcode (=$68)
  36.     Addr: pointer;  // The actual address of the DLL routine
  37.     JMP : byte;     // JMP instruction opcode (=$E9)
  38.     Rel : Integer;  // Relative displacement (a Kernel32 address)
  39.   end;
  40.  
  41. function IsWin95CallThunk(Thunk: PWin95CallThunk): boolean;
  42. begin
  43.   Result := (Thunk^.PUSH = $68) and (Thunk^.JMP = $E9);
  44. end;
  45.  
  46. function ReplaceImport(Base: Pointer; ModuleName: PChar; FromProc, ToProc: pointer): boolean;
  47. var
  48.   NtHeader          : PImageNtHeaders;
  49.   ImportDescriptor  : PImageImportDescriptor;
  50.   ImportEntry       : PImageThunkData;
  51.   CurrModuleName    : PChar;
  52.   IsThunked         : Boolean;
  53.   FromProcThunk     : PWin95CallThunk;
  54.   ImportThunk       : PWin95CallThunk;
  55.   FoundProc         : boolean;
  56. begin
  57.   // Assume failure
  58.   Result := false;
  59.  
  60.   // Cache some Win95-specific knowledge about the FromProc
  61.   // On Win95/98, the import entries and GetProcAddress of some system
  62.   // module routines point to PUSH [ActualAdress]/JMP [Rel] thunks,
  63.   // so we have to look into the code to see if the final target matches
  64.   // Convert the FromProc into the thunk code it /might/ point to
  65.   FromProcThunk := PWin95CallThunk(FromProc);
  66.   // Is it a valid Win95 thunk?
  67.   IsThunked := (Win32Platform = VER_PLATFORM_WIN32_WINDOWS) and
  68.                IsWin95CallThunk(FromProcThunk);
  69.  
  70.   // Get a pointer to the PE-header
  71.   NtHeader := GetImageNtHeader(Base);
  72.   // Get a pointer to the import descriptor table
  73.   ImportDescriptor := PImageImportDescriptor(DWORD(Base)+
  74.     NtHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
  75.  
  76.   // The table of import descriptors is marked with a null name offset
  77.   while ImportDescriptor^.NameOffset <> 0 do
  78.   begin
  79.     // Calculate a pointer to this module name
  80.     CurrModuleName := PChar(Base) + ImportDescriptor^.NameOffset;
  81.     // Only search matching modules
  82.     if StrIComp(CurrModuleName, ModuleName) = 0 then
  83.     begin
  84.  
  85.       // Calculate a pointer to the first import entry for this module
  86.       ImportEntry := PImageThunkData(DWORD(Base) + ImportDescriptor^.IATOffset);
  87.  
  88.       // Loop until we have reached the end of the list
  89.       while ImportEntry^.FunctionPtr <> nil do
  90.       begin
  91.         // Now we must determine if this import entry pointer
  92.         // is equivalent to the FromProc address
  93.  
  94.         if IsThunked then
  95.         begin
  96.           // Convert the ImportEntry into the thunk code it /might/ point to
  97.           ImportThunk := PWin95CallThunk(ImportEntry^.FunctionPtr);
  98.  
  99.           // If the routine we're hooking points to a Win95 thunk,
  100.           // see if the final target matches
  101.           FoundProc := IsWin95CallThunk(ImportThunk) and
  102.                        (ImportThunk^.Addr = FromProcThunk^.Addr)
  103.         end
  104.         else
  105.           // otherwise, only check for clean matches
  106.           FoundProc := (ImportEntry^.FunctionPtr = FromProc);
  107.  
  108.         // If we found the correct import entry, patch it!
  109.         if FoundProc then
  110.         begin
  111.           // Note that all import sections have Read/Write access by default,
  112.           // so there is no need to play around with WriteProcessMemory or
  113.           // VirtualProtect
  114.           ImportEntry^.FunctionPtr := ToProc;
  115.  
  116.           // There could be more imports of the same routine, so just flag
  117.           // success and keep looking for more matches
  118.           Result := true;
  119.         end;
  120.  
  121.         // Look at the next Import Entry for this module
  122.         Inc(ImportEntry);
  123.       end;
  124.     end;
  125.     // Look at the next Import Descriptor
  126.     Inc(ImportDescriptor);
  127.   end;
  128. end;
  129.  
  130. function HookImport(ModuleName, ImportName: PChar; HookProc: pointer; var DLLProc: pointer): boolean;
  131. begin
  132.   Result := not Assigned(DLLProc);
  133.   if Result then
  134.   begin
  135.     DllProc := Windows.GetProcAddress(Windows.GetModuleHandle(ModuleName), ImportName);
  136.     Result := Assigned(DllProc) and
  137.               ReplaceImport(Pointer(HInstance), ModuleName, DllProc, HookProc);
  138.     if not Result then
  139.       DLLProc := nil;
  140.   end;
  141. end;
  142.  
  143. function UnhookImport(ModuleName, ImportName: PChar; HookProc: pointer; var DLLProc: pointer): boolean;
  144. begin
  145.   Result := Assigned(DllProc) and
  146.             ReplaceImport(Pointer(HInstance), ModuleName, HookProc, DllProc);
  147.   if Result then
  148.     DLLProc := nil;
  149. end;
  150.  
  151. end.
  152.  
  153.  
  154.